<?php

namespace app\adminapi\controller;

use app\libs\database\Database as Databases;

use think\facade\Db;
use think\facade\Config;
use think\Request;

class Database extends Base
{
    /**
     * 备份配置
     */
    private $config;

    /**
     * 数据库备份初始化
     */
    public function initialize()
    {
        parent::initialize();

        // 读取备份配置
        $config = array(
            'file_path' => Config::get('path.db_file_path'),  // 文件路径
            'path' => Config::get('path.db_path'),  // 绝对路径
            'part' => Config::get('path.db_part'),  // 分卷大小 20M
            'compress' => Config::get('path.db_compress'),  // 0:不压缩 1:启用压缩
            'level' => Config::get('path.db_level'),  // 压缩级别, 1:普通 4:一般  9:最高
        );

        $this->config = $config;
    }

    /**
     * 数据库备份列表
     */
    public function exportList(Request $request)
    {
        //$list = Db::query('SHOW TABLES');
        $list = Db::query('SHOW TABLE STATUS');
        $datas = [];
        if ($list) {
            // 处理列表数据
            foreach ($list as $key => $table) {
                $datas[] = [
                    'id' => $key + 1,
                    'name' => $table['Name'],
                    'engine'=> $table['Engine'],
                    'rows' => $table['Rows'],
                    'data_free' => $table['Data_free'],
                    'index_length' => $table['Index_length'],
                    'collation' => $table['Collation'],
                    'comment' => $table['Comment'],
                    'create_time' => $table['Create_time'],
                    'data_length' => format_bytes($table['Data_length']),
                ];
            }
        }
        $data = [
            'data' => $datas
        ];

        return api_response(self::STATUS_SUCCESS, '', $data);
    }

    /**
     * 数据库表结构
     */
    public function view(Request $request)
    {
        $param = $request->param();
        if (empty($param['table'])) {
            return api_response(self::STATUS_ERROR, '表名(table)不能为空', self::NOT_PARAM);
        }

        $sql = "SELECT * FROM `information_schema`.`columns` WHERE TABLE_SCHEMA = ? AND table_name = ? ORDER BY ORDINAL_POSITION";
        $list = Db::query($sql, [config('database.db_database'), $param['table']]);
        $datas = [];
        if ($list) {
            // 处理列表数据
            foreach ($list as $key => $column) {
                $datas[] = [
                    'id' => $key + 1,
                    'column_key' => $column['COLUMN_KEY'],
                    'column_name'=> $column['COLUMN_NAME'],
                    'column_type' => $column['COLUMN_TYPE'],
                    'column_comment' => $column['COLUMN_COMMENT'],
                    'data_type' => $column['DATA_TYPE'],
                    'extra' => $column['EXTRA'],
                    'is_nullable' => $column['IS_NULLABLE'],
                ];
            }
        }
        $data = [
            'data' => $datas
        ];

        return api_response(self::STATUS_SUCCESS, '', $data);
    }

    /**
     * 数据库还原列表
     */
    public function importList(Request $request)
    {
        // 判断目录是否存在
        is_writeable($this->config['path']) || mkdir($this->config['path'], 0777, true);
        $pattern = "{*.sql,*.gz}";
        $fileList = glob($this->config['path'] . $pattern, GLOB_BRACE);
        $datas = [];
        foreach ($fileList as $i => $file) {
            // 只读取文件
            if (is_file($file)) {
                $size = filesize($file);
                $filename = basename($file);
                $filename_arr = explode('-', $filename);
                $name = substr($filename, 0, strrpos($filename, '-'));
                $extension = strtoupper(pathinfo($filename, PATHINFO_EXTENSION));
                $search = [$name . '-', '.sql', '.gz'];
                $replace = ['', '', ''];
                $part = str_replace($search, $replace, $filename);
                //$time = filemtime($file); // 上次修改时间
                //$time = filectime($file); // 创建时间
                $time = $filename_arr[1];
                $datas[] = [
                    'numbers' => $i + 1,
                    'full_filepath' => asset($this->config['file_path'] . $filename),
                    'filepath' => $filename,
                    'name' => $name,
                    'part' => $part,
                    //'size' => $size,
                    'size' => format_bytes($size),
                    'compress' => ($extension === 'SQL') ? '-' : $extension,
                    'time' => $time,
                    'mtime' => date('Y-m-d H:i:s', strtotime($time))
                ];
            }
        }
        // 排序
        $sortKeys = array_column($datas, 'time');
        array_multisort($sortKeys, SORT_DESC, $datas);
        foreach ($datas as $key => &$item) {
            $item['numbers'] = $key + 1;
        }

        $data = [
            'data' => $datas
        ];

        return api_response(self::STATUS_SUCCESS, '', $data);
    }

    /**
     * 优化表
     */
    public function optimize(Request $request)
    {
        $param = $request->param();
        if (empty($param['tables'])) {
            return api_response(self::STATUS_ERROR, '表名(tables)不能为空', self::NOT_PARAM);
        }

        $tables = (array)$param['tables'];
        $tables = implode('`,`', $tables);
        $list = Db::query("OPTIMIZE TABLE `{$tables}`");
        if (!$list) {
            return api_response(self::STATUS_ERROR, '数据表优化出错请重试！', '');
        }

        return api_response(self::STATUS_SUCCESS, '数据表优化完成！', []);
    }

    /**
     * 修复表
     */
    public function repair(Request $request)
    {
        $param = $request->param();
        if (empty($param['tables'])) {
            return api_response(self::STATUS_ERROR, '表名(tables)不能为空', self::NOT_PARAM);
        }

        $tables = (array)$param['tables'];
        $tables = implode('`,`', $tables);
        $list = Db::query("REPAIR TABLE `{$tables}`");
        if (!$list) {
            return api_response(self::STATUS_ERROR, '数据表修复出错请重试！', '');
        }

        return api_response(self::STATUS_SUCCESS, '数据表修复完成！', []);
    }

    /**
     * 删除备份文件
     */
    public function delete(Request $request)
    {
        $param = $request->param();
        if (empty($param['time'])) {
            return api_response(self::STATUS_ERROR, self::NOT_PARAM, '');
        }

        // 以时间命名的备份文件
        $time = $param['time'];
        $name = '*' . $time . '-*.sql*';
        $path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
        array_map('unlink', glob($path));
        if (count(glob($path))) {
            return api_response(self::STATUS_ERROR, '备份文件删除失败，请检查权限！', '');
        }

        return api_response(self::STATUS_SUCCESS, '备份文件删除成功！', '');
    }

    /**
     * 备份数据库
     */
    public function doExport(Request $request)
    {
        $param = $request->param();
        if (empty($param['tables'])) {
            //return api_response(self::STATUS_ERROR, self::NOT_PARAM, '');
        }
        $tables = $param['tables'] ?? [];
        $id = $param['id'] ?? null;
        $start = $param['start'] ?? null;
        $cacheFileKey = 'backup_file';
        $cacheTableKey = 'backup_tables'; // 要备份的表
        if ($tables && is_null($id) && is_null($start)) {
            // 初始化
            // 读取备份配置
            $config = $this->config;
            // 检查备份目录是否可写 创建备份目录
            is_writeable($config['path']) || mkdir($config['path'], 0777, true);
            // 检查是否有正在执行的任务
            $lock = $config['path'] . 'backup.lock';
            if (is_file($lock)) {
                return api_response(self::STATUS_ERROR, '检测到有一个备份任务正在执行，请稍后再试！', '');
            }
            // 创建锁文件
            file_put_contents($lock, time());
            // 生成备份文件信息
            $file = [
                'name' => (count($tables) > 1) ? 'multi_tables-' . date('YmdHis', time()) : $tables[0] . '-' . date('YmdHis', time()),
                'part' => 1
            ];
            // 创建备份文件
            $dbObj = new Databases($file, $config);
            if ($dbObj->create() === false) {
                return api_response(self::STATUS_ERROR, '初始化失败，备份文件创建失败！', '');
            }

            //cache('backup_config', $config);
            cache($cacheFileKey, $file);
            cache($cacheTableKey, $tables);

            $data = [
                'status' => 1,
                'info' => '初始化成功！',
                //'tables' => $tables,
                'tab' => ['name' => $tables[0], 'id' => 0, 'start' => 0]
            ];

            return api_response(self::STATUS_SUCCESS, '', $data);
        } elseif ( is_numeric($id) && is_numeric($start)) {
            // 备份数据
            $file = cache($cacheFileKey);
            $tables = cache($cacheTableKey);
            $config = $this->config;
            // 备份指定表
            $dbObj = new Databases($file, $config);
            $start = $dbObj->backup($tables[$id], $start);
            if ($start === false) {
                return api_response(self::STATUS_ERROR, '备份出错！', '');
            }
            if ($start === 0) {
                // 下一表
                if (isset($tables[++$id])) {
                    $data = [
                        'status' => 1,
                        'info' => '备份完成！',
                        'tab' => ['name' => $tables[$id], 'id' => $id, 'start' => 0]
                    ];

                    return api_response(self::STATUS_SUCCESS, '', $data);
                } else {
                    // 备份完成，清空缓存
                    unlink($config['path'] . 'backup.lock');
                    cache($cacheFileKey, null);
                    cache($cacheTableKey, null);

                    $data = [
                        'status' => 1,
                        'info' => '备份完成！'
                    ];

                    return api_response(self::STATUS_SUCCESS, '', $data);
                }
            } else {
                $rate = floor(100 * ($start[0] / $start[1]));
                $data = [
                    'status' => 1,
                    'info' => "正在备份...({$rate}%)",
                    'tab' => ['name' => $tables[$id], 'id' => $id, 'start' => $start[0]]
                ];

                return api_response(self::STATUS_SUCCESS, '', $data);
            }
        } else {
            return api_response(self::STATUS_ERROR, '参数错误！', '');
        }
    }

    /**
     * 还原数据库
     */
    public function doImport(Request $request)
    {
        $param = $request->param();
        if (empty($param['time'])) {
            return api_response(self::STATUS_ERROR, self::NOT_PARAM, '');
        }
        $time = $param['time'];
        $part = $param['part'] ?? null;
        $start = $param['start'] ?? null;
        $cacheListKey = 'backup_list_' . $time; // 备份列表
        if (is_null($part) && is_null($start)) {
            // 初始化
            // 获取备份文件信息
            $name = '*' . $time . '-*.sql*';
            $path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
            $files = glob($path);
            $list = array();
            foreach ($files as $name) {
                $basename = basename($name);
                // 表名前缀
                $basename = substr(strstr($basename, '-'), 1);
                $match = sscanf($basename, '%4s%2s%2s%2s%2s%2s-%d');
                $gz = preg_match('/^db_\d{8,8}\d{6,6}-\d+\.sql.gz$/', $basename);
                $list[$match[6]] = array($match[6], $name, $gz);
            }
            ksort($list);
            // 检测文件正确性
            $last = end($list);
            if (!$files || count($list) !== $last[0]) {
                return api_response(self::STATUS_ERROR, '备份文件可能已经损坏，请检查！', '');
            }
            cache($cacheListKey, $list);

            $data = [
                'status' => -1,
                'info' => '',
                'part' => 1,
                'start' => 0,
                'time' => $time
            ];

            return api_response(self::STATUS_SUCCESS, '', $data);
        } elseif (is_numeric($part) && is_numeric($start)) {
            $list = cache($cacheListKey);
            $dbObj = new Databases($list[$part], [
                'path' => realpath($this->config['path']) . DIRECTORY_SEPARATOR,
                'compress' => $list[$part][2]
            ]);
            $start = $dbObj->import($start);
            if ($start === false) {
                return api_response(self::STATUS_ERROR, '还原数据出错！', '');
            }
            if ($start === 0) {
                // 下一卷
                if (isset($list[++$part])) {
                    $data = [
                        'status' => 1,
                        'info' => "正在还原...#{$part}",
                        'part' => $part,
                        'start' => 0
                    ];

                    return api_response(self::STATUS_SUCCESS, '', $data);
                }

                cache($cacheListKey, null);
                $data = [
                    'status' => 1,
                    'info' => '还原完成！'
                ];

                return api_response(self::STATUS_SUCCESS, '', $data);
            } else {
                if ($start[1]) {
                    $rate = floor(100 * ($start[0] / $start[1]));
                    $data = [
                        'status' => 1,
                        'info' => "正在还原...#{$part} ({$rate}%)",
                        'part' => $part,
                        'start' => $start[0]
                    ];

                    return api_response(self::STATUS_SUCCESS, '', $data);
                } else {
                    $data = [
                        'status' => 1,
                        'info' => "正在还原...#{$part}",
                        'part' => $part,
                        'start' => $start[0],
                        'gz' => 1
                    ];

                    return api_response(self::STATUS_SUCCESS, '', $data);
                }
            }
        } else {
            return api_response(self::STATUS_ERROR, '参数错误！', '');
        }
    }
}
